﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SharpSoft.OS
{
    public partial class Linux
    {
        class wpa_cli_cmds
        {
            /// <summary>
            /// scans for available access points (only scans it, doesn‘t display anything)
            /// </summary>
            public const string scan = "sudo wpa_cli scan";
            /// <summary>
            /// displays the results of the last scan
            /// </summary>
            public const string scan_result = "sudo wpa_cli scan_result";
            /// <summary>
            /// displays a list of configured networks and their status (active or not, enabled or disabled)
            /// </summary>
            public const string list_networks = "sudo wpa_cli list_networks";
            /// <summary>
            /// displays the current connection status
            /// </summary>
            public const string status = "sudo wpa_cli status";
            /// <summary>
            /// prevents wpa_supplicant from connecting to any access point
            /// </summary>
            public const string disconnect = "sudo wpa_cli disconnect";
            /// <summary>
            /// exits wpa_cli
            /// </summary>
            public const string quit = "sudo wpa_cli quit";
            /// <summary>
            /// kills wpa_supplicant
            /// </summary>
            public const string terminate = "sudo wpa_cli terminate";
            /// <summary>
            /// reloads wpa_supplicant with the configuration file supplied (-c parameter)
            /// </summary>
            public const string reconfigure = "sudo wpa_cli reconfigure";
            /// <summary>
            /// select a network among those defined to initiate a connection (ie select_network 0)
            /// </summary>
            public const string select_network = "sudo wpa_cli select_network {0}";
            /// <summary>
            /// makes a configured network available for selection (ie enable_network 0)
            /// </summary>
            public const string enable_network = "sudo wpa_cli enable_network {0}";
            /// <summary>
            /// makes a configured network unavailable for selection (ie disable_network 0)
            /// </summary>
            public const string disable_network = "sudo wpa_cli disable_network {0}";
            /// <summary>
            /// removes a network and its configuration from the list (ie remove_network 0)
            /// </summary>
            public const string remove_network = "sudo wpa_cli remove_network {0}";
            /// <summary>
            /// adds a new network to the list. Its id will be created automatically
            /// </summary>
            public const string add_network = "sudo wpa_cli add_network";
            /// <summary>
            /// shows a very short list of available options to configure a network when supplied with no parameters. 
            /// See next section for a list of extremely useful parameters to be //used with set_network and get_network.
            /// </summary>
            public const string set_network = "sudo wpa_cli set_network {0} {1} {2}";
            /// <summary>
            /// displays the required parameter for the specified network. See nextsection for a list of parameters
            /// </summary>
            public const string get_network = "sudo wpa_cli get_network {0}";
            /// <summary>
            /// saves the configuration
            /// </summary>
            public const string save_config = "sudo wpa_cli save_config";
        }

        public static wpaStation[] scan()
        {
            var result = Linux.shell(wpa_cli_cmds.scan);
            result = parseResult(result);
            checkOk(result);
            result = Linux.shell(wpa_cli_cmds.scan_result);
            result = parseResult(result);
            return parseScanResult(result);
        }

        public static int add_network()
        {
            var result = Linux.shell(wpa_cli_cmds.add_network);
            result = parseResult(result);
            try
            {
                var id = int.Parse(result);
                return id;
            }
            catch (Exception)
            {
                throw new Exception("addnetwork failed.");
            }


        }

        private static void set_network(int id, string variable, string value)
        {
            var cmd = string.Format(wpa_cli_cmds.set_network, id, variable, value);
            var result = Linux.shell(cmd);
            result = parseResult(result);
            try
            {
                checkOk(result);
            }
            catch (Exception)
            {
                throw new Exception($"{cmd}  Result:[{result}]");
            }
        }
        private static void select_network(int id)
        {
            var cmd = string.Format(wpa_cli_cmds.select_network, id);
            var result = Linux.shell(cmd);
            checkOk(result);
        }
        private static void enable_network(int id)
        {
            var cmd = string.Format(wpa_cli_cmds.enable_network, id);
            var result = Linux.shell(cmd);
            checkOk(result);
        }
        private static void disable_network(int id)
        {
            var cmd = string.Format(wpa_cli_cmds.disable_network, id);
            var result = Linux.shell(cmd);
            checkOk(result);
        }
        public static void list_network()
        {
            var result = Linux.shell(wpa_cli_cmds.list_networks);
            result = parseResult(result);
        }
        private static void save_config()
        {
            var result = Linux.shell(wpa_cli_cmds.save_config);
        }
        private static void reconfigure()
        {
            var result = Linux.shell(wpa_cli_cmds.reconfigure);
            result = parseResult(result);
            checkOk(result);

        }
        private static string getHexStr(string source)
        {
            var data = Encoding.UTF8.GetBytes(source);
            StringBuilder sb = new StringBuilder(data.Length * 2);
            foreach (var b in data)
            {
                sb.Append($"{b:X00}");
            }
            return sb.ToString();
        }
        private static string getSecurityAlgorithm(wpaSecurityAlgorithm securityAlgorithm)
        {
            switch (securityAlgorithm)
            {
                case wpaSecurityAlgorithm.TKIP:
                    return "TKIP";
                case wpaSecurityAlgorithm.AES:
                    return "CCMP";
                case wpaSecurityAlgorithm.TKIP_AES:
                    return "CCMP TKIP";
                case wpaSecurityAlgorithm.None:

                    return "";
            }
            return "";
        }
        /// <summary>
        /// 连接到接入点。SEE:http://www.cnblogs.com/lidabo/p/5062190.html
        /// </summary>
        /// <param name="station">要接入的连入点</param>
        /// <param name="psw">密码</param>
        public static void ConnectToStation(wpaStation station, string psw)
        {
            wpaSecurityAlgorithm securityAlgorithm;
            var safemode = station.GetSafeMode(out securityAlgorithm);
            var nid = add_network();
            set_network(nid, "ssid", $"'\"{station.ssid}\"'");
            set_network(nid, "bssid", $"{station.bssid}");
            if (station.hidden)
            {
                set_network(nid, "scan_ssid", "1");
            }
            switch (safemode)
            {
                case wpaSafeMode.None:
                    set_network(nid, "key_mgmt", "NONE");
                    break;
                case wpaSafeMode.Wep:
                    set_network(nid, "key_mgmt", "NONE");
                    set_network(nid, "wep_key0", getHexStr(psw));
                    set_network(nid, "wep_tx_keyidx", "0");
                    break;
                case wpaSafeMode.WPA_PSK:
                    set_network(nid, "key_mgmt", "WPA-PSK");
                    set_network(nid, "pairwise", getSecurityAlgorithm(securityAlgorithm));
                    set_network(nid, "group", "CCMP TKIP WEP104 WEP40");
                    set_network(nid, "psk", $"'\"{psw}\"'");
                    set_network(nid, "priority", "2");
                    break;
                case wpaSafeMode.WPA2_PSK:
                    set_network(nid, "key_mgmt", "WPA2-PSK");
                    set_network(nid, "pairwise", getSecurityAlgorithm(securityAlgorithm));
                    set_network(nid, "group", "CCMP TKIP WEP104 WEP40");
                    set_network(nid, "psk", $"'\"{psw}\"'");
                    set_network(nid, "priority", "2");
                    break;
                case wpaSafeMode.WPA_WPA2_PSK:
                    //// set_network(nid, "key_mgmt", "WPA-PSK WPA2-PSK");
                    set_network(nid, "pairwise", getSecurityAlgorithm(securityAlgorithm));
                    set_network(nid, "group", "CCMP TKIP WEP104 WEP40");
                    set_network(nid, "psk", getHexStr(psw));
                    set_network(nid, "priority", "2");
                    break;
            }

            select_network(nid);
            enable_network(nid);

        }

        private static wpaStation[] parseListResult(string result)
        {
            var rows = result.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var row in rows)
            {
                wpaStation sta = new wpaStation();
                var fields = row.Split('\t');
                 
            }

            return null;
        }
        private static wpaStation[] parseScanResult(string result)
        {
            int firstbreak = result.IndexOf('\n');
            result = result.Substring(firstbreak, result.Length - firstbreak).Trim();
            var ss = result.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
            wpaStation[] stas = new wpaStation[ss.Length];
            for (int j = 0; j < ss.Length; j++)
            {
                var s = ss[j];
                var props = s.Split(new char[] { '\t' });
                wpaStation sta = new wpaStation();
                sta.bssid = props[0];
                sta.frequency = int.Parse(props[1]);
                sta.signallevel = int.Parse(props[2]);
                sta.ssid = props[4];
                var flags = props[3];
                var fs = flags.Split(new string[] { "][" }, StringSplitOptions.None);
                for (int i = 0; i < fs.Length; i++)
                {
                    fs[i] = fs[i].Trim('[', ']');
                }
                sta.flags = fs;
                stas[j] = sta;
            }
            return stas;
        }
        private static string parseResult(string result)
        {
            int firstbreak = result.IndexOf('\n');
            return result.Substring(firstbreak, result.Length - firstbreak).Trim();
        }

        static void checkOk(string msg)
        {
            if (msg == "OK")
            {

            }
            else
            {
                throw new Exception("command execute failed with messgae:" + msg);
            }
        }
    }
    public enum wpaSafeMode
    {
        None,
        Wep,
        WPA_PSK,
        WPA2_PSK,
        WPA_WPA2_PSK
    }
    public enum wpaSecurityAlgorithm
    {
        TKIP,
        AES,
        TKIP_AES,
        None
    }
    public class wpaStation
    {
        public int networkid { get; set; }
        public bool hidden { get; set; }
        public string ssid { get; set; }

        public string bssid { get; set; }
        /// <summary>
        /// 频道
        /// </summary>
        public int frequency { get; set; }
        /// <summary>
        /// 信号强度
        /// </summary>
        public int signallevel { get; set; }

        public string[] flags { get; set; }
        /// <summary>
        /// 获取安全模式
        /// </summary>
        /// <param name="securityAlgorithm"></param>
        /// <returns></returns>
        public wpaSafeMode GetSafeMode(out wpaSecurityAlgorithm securityAlgorithm)
        {
            bool wpa = false, wpa2 = false;
            if (flags != null)
            {
                string sec = "";
                foreach (var item in flags)
                {
                    if (item == "WEP")
                    {
                        securityAlgorithm = wpaSecurityAlgorithm.None;
                        return wpaSafeMode.Wep;
                    }
                    else if (item.StartsWith("WPA2"))
                    {
                        wpa2 = true;
                        var ps = item.Split('-');
                        if (ps.Length == 3)
                        {
                            sec = ps[2];
                        }
                    }
                    else if (item.StartsWith("WPA"))
                    {
                        var ps = item.Split('-');
                        if (ps.Length == 3)
                        {
                            sec = ps[2];
                        }
                        wpa = true;
                    }
                }
                wpaSafeMode m;
                if (wpa && wpa2)
                {
                    m = wpaSafeMode.WPA_WPA2_PSK;
                }
                else if (wpa2)
                {
                    m = wpaSafeMode.WPA2_PSK;
                }
                else if (wpa)
                {
                    m = wpaSafeMode.WPA_PSK;
                }
                else
                {
                    m = wpaSafeMode.None;
                }
                wpaSecurityAlgorithm sa = wpaSecurityAlgorithm.None;
                if (sec == "CCMP")
                {
                    sa = wpaSecurityAlgorithm.AES;
                }
                else if (sec == "TKIP")
                {
                    sa = wpaSecurityAlgorithm.TKIP;
                }
                else if (sec == "CCMP+TKIP")
                {
                    sa = wpaSecurityAlgorithm.TKIP_AES;
                }
                securityAlgorithm = sa;
                return m;
            }
            securityAlgorithm = wpaSecurityAlgorithm.None;
            return wpaSafeMode.None;
        }
    }
}
